#lang errortrace racket
;; Values
(define (r:value? v)
  (or (r:number? v)))
(struct r:number (value) #:transparent)
;; Expressions
(define (r:expression? e)
  (or (r:value? e)
      (r:variable? e)
      (r:apply? e)))
(struct r:variable (name) #:transparent)
(struct r:apply (func args) #:transparent)

;; Program
(define (j:program? t)
  (or (j:assign? t)
      (j:seq? t)
      (j:while? t)
      (j:log? t)))
(struct j:assign (var exp) #:transparent)
(struct j:while (exp body) #:transparent)
(struct j:log (exp) #:transparent)
(struct j:seq (left right) #:transparent)

(define (r:eval-builtin vars sym)
  (cond [(equal? sym '+) +]
        [(equal? sym '*) *]
        [(equal? sym '-) -]
        [(equal? sym '/) /]
        [else (hash-ref vars (r:variable sym))]))

(define (r:eval vars exp)
  (cond
    [(r:number? exp) (r:number-value exp)]
    [(r:variable? exp) (r:eval-builtin vars (r:variable-name exp))]
    [(r:apply? exp)
     ((r:eval vars (r:apply-func exp))
      (r:eval vars (first (r:apply-args exp)))
      (r:eval vars (second (r:apply-args exp))))]
    [else (error "Unknown expression:" exp)]))


(define/contract (j:eval vars prog)
  (-> hash? j:program? hash?)
  (cond
    [(j:assign? prog) (hash-set vars (j:assign-var prog) (r:eval vars (j:assign-exp prog)))]
    [(j:seq? prog)
      (j:eval (j:eval vars (j:seq-left prog)) (j:seq-right prog))]
    [(j:log? prog) (displayln (r:eval vars (j:log-exp prog))) vars]
    [(j:while? prog)
      (define v (r:eval vars (j:while-exp prog)))
      (cond [(equal? v 0) vars]
            [else (j:eval vars (j:seq (j:while-body prog) prog))])]))

(define p
(j:seq
  (j:assign (r:variable 'x) (r:number 10))
  (j:while (r:variable 'x)
      (j:seq
        (j:log (r:variable 'x))
        (j:assign (r:variable 'x)
            (r:apply
              (r:variable '-)
              (list
                (r:variable 'x)
                (r:number 1)))))))
)

(j:eval (hash) p)
